Object Wrapping
Kamu sudah sampai jauh di kursus ini—mantap! Setelah memahami dasar dan membedah Hello World, saatnya masuk topik lebih lanjut. Di pelajaran ini kita bahas object wrapping, teknik penting saat membangun dApp di Sui.
Apa itu Object Wrapping?
Di Sui Move, kita bisa menaruh sebuah Sui object di dalam object lain—ibarat toolbox berisi item di dalamnya. Secara teknis: menaruh field bertipe struct ke dalam struct lain.
public struct Foo has key {
id: UID,
bar: Bar,
}
public struct Bar has key, store {
id: UID,
value: u64,
}
Agar struct menjadi Sui object, ia harus punya ability key. Jika Foo (Sui object) menyimpan field bertipe Bar (Sui object), maka Foo membungkus Bar—Foo disebut wrapper.
Catatan penting:
- Menaruh Sui object ke dalam non-Sui struct hanya bersifat sementara (tidak persisten on-chain).
- Objek yang ter-wrap menjadi bagian data wrapper: tidak bisa dicari via UID-nya langsung dan tidak bisa dipass terpisah dalam transaksi—aksesnya lewat wrapper.
- Jangan membuat siklus wrapping (A membungkus B, B membungkus C, C membungkus A).
- Objek bisa di-unwrap kembali menjadi entitas independen dengan UID yang sama seperti sebelum dibungkus.
There are a few common ways to wrap a Sui object into another Sui object. Let’s look at them below:
Direct Wrapping
Jika Anda menaruh sebuah tipe objek Sui secara langsung sebagai sebuah field dalam tipe objek Sui lainnya, itu disebut direct wrapping. Pada jenis pembungkusan ini, objek yang dibungkus tidak dapat di-unwrap kecuali objek pembungkusnya dihancurkan. Ini dapat digunakan untuk mengunci objek dengan akses terbatas.
Contoh: anggap ada object gaya NFT bernama Object dengan scarcity dan style. Untuk fair trade, kita ingin hanya style yang beda sementara scarcity harus sama.
public struct Object has key, store {
id: UID,
scarcity: u8,
style: u8,
}
entry fun create_object(scarcity: u8, style: u8, ctx: &mut TxContext) {
let object = Object {
id: object::new(ctx),
scarcity,
style,
};
transfer::transfer(object, tx_context::sender(ctx));
}
Dengan create_object, siapa pun bisa mencetak object dan mengirimkannya ke pengirim transaksi.
Hanya pemilik yang boleh memutasi object. Untuk swap, biasanya objek dikirim ke pihak ketiga (service). Agar kustodi tetap aman (service tidak bisa mencuri), gunakan direct wrapping:
public struct ObjectWrapper has key {
id: UID,
original_owner: address,
to_swap: Object,
fee: Balance<SUI>,
}
ObjectWrapper membungkus object yang hendak ditukar (to_swap) dan mencatat pemilik asal. Ada juga field fee.
To request a swap, you define an entry function:
entry fun request_swap(object: Object, fee: Coin<SUI>, service_address: address, ctx: &mut TxContext) {
assert!(coin::value(&fee) >= MIN_FEE, 0);
let wrapper = ObjectWrapper {
id: object::new(ctx),
original_owner: tx_context::sender(ctx),
to_swap: object,
fee: coin::into_balance(fee),
};
transfer::transfer(wrapper, service_address);
}
Fungsi tersebut membungkus object beserta fee dan mengirim wrapper ke alamat service.
Poin krusial: meski service memegang ObjectWrapper, ia tidak bisa mengakses/mengambil Object di dalamnya tanpa fungsi unwrapping yang spesifik—dan fungsi itu tidak disediakan di module ini.
Wrapping via Option
Dalam contoh direct wrapping (pembungkusan langsung) di atas, Anda harus menghancurkan objek pembungkus untuk mengeluarkan objek yang ada di dalamnya. Namun, terkadang Anda mungkin menginginkan fleksibilitas lebih, di mana tipe pembungkus tidak selalu berisi objek yang dibungkus di dalamnya, dan Anda bisa mengganti objek yang dibungkus dengan objek lain saat diperlukan.
Contoh: seorang warrior bisa punya atau tidak punya sword/shield, dan dapat mengganti peralatannya.
Pada kode di bawah, kita mendefinisikan tipe SimpleWarrior dengan field opsional sword dan shield. sword dan shield adalah tipe objek Sui lainnya.
public struct SimpleWarrior has key {
id: UID,
sword: Option<Sword>,
shield: Option<Shield>,
}
public struct Sword has key, store {
id: UID,
strength: u8,
}
public struct Shield has key, store {
id: UID,
armor: u8,
}
When you create a new warrior, you start with no equipment:
entry fun create_warrior(ctx: &mut TxContext) {
let warrior = SimpleWarrior {
id: object::new(ctx),
sword: option::none(),
shield: option::none(),
};
transfer::transfer(warrior, tx_context::sender(ctx))
}
Fungsi create_warrior membuat warrior tanpa peralatan.
Untuk menggunakan sword atau shield, kamu bisa mendefinsikan fungsi seperti dibawah ini:
entry fun equip_sword(warrior: &mut SimpleWarrior, sword: Sword, ctx: &mut TxContext) {
if (option::is_some(&warrior.sword)) {
let old_sword = option::extract(&mut warrior.sword);
transfer::transfer(old_sword, tx_context::sender(ctx));
};
option::fill(&mut warrior.sword, sword);
}
Pada equip_sword, jika sudah ada pedang, keluarkan dulu dan kembalikan ke pengirim, lalu isi dengan pedang baru. Jadi peralatan bisa diganti dinamis.
Wrapping via vector
Di Sui, kamu bisa membungkus (wrap) objek ke dalam field vector milik objek Sui lain, yang mirip dengan membungkus objek menggunakan tipe Option. Pendekatan ini memungkinkan sebuah objek untuk berisi nol, satu, atau banyak objek yang dibungkus dengan tipe yang sama.
Mari kita lihat contoh yang melibatkan hewan peliharaan (pet) dan sebuah peternakan (farm):
struct Pet has key, store {
id: UID,
cuteness: u64,
}
struct Farm has key {
id: UID,
pets: vector<Pet>,
}
Pada kode di atas, ada dua tipe objek:
Pet: Merepresentasikan hewan peliharaan individu dan memiliki ID unik serta nilai "cuteness" (kelucuan), yang di sini hanya sebagai contoh karakteristik hewan.Farm: Merepresentasikan sebuah peternakan dan juga memiliki ID unik. Fitur kunci di sini adalah fieldpets, yang merupakan vector dari objekPet. Artinya, sebuah peternakan bisa berisi banyak hewan peliharaan.
Dengan ini, satu farm bisa menampung banyak Pet di field pets.
Penutup
Kamu sudah memahami konsep wrapping: direct, via Option, dan via vector. Berikutnya kita pelajari event di Move on Sui.